#ifndef __ANY_H__
#define __ANY_H__

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <memory.h>
#include "common.h"
#include "type.h"

typedef class any_t any_t;
typedef struct ref_t ref_t;
typedef struct pair_t pair_t;
typedef class array_t array_t;
typedef class dict_t dict_t;
struct function_t;
struct param_t;
struct field_t;
struct method_t;
struct class_def_t;
class class_obj_t;

void add_ref(any_t* a);
void sub_ref(any_t* a);
int get_ref(any_t* a);
bool is_no_ref(any_t* a);

enum class atype_t
{
	ANY_VOID,
	ANY_NONE,
	ANY_BOOL,
	ANY_CHAR,
	ANY_I8,
	ANY_DOUBLE,
	ANY_STRING,
	ANY_ARRAY,
	ANY_DICT,
	ANY_PAIR,
	ANY_REF,
	ANY_CLASS,
	ANY_METHOD,
    ANY_COUNT,
};

const char* get_type_name(atype_t type);

extern int g_any_t_id;

class any_t
{
#ifdef DEBUG
    int id = g_any_t_id++;
#endif
public:
    atype_t type;   // LONGLONG or DOUBLE
	union {
		int i[2];
		float f;
		double d;
		void *p;
		uint64_t u8;
		int64_t i8;
		const char* s;
        any_t* ref;
		array_t* array;
		dict_t* dict;
		pair_t* pair;
		class_obj_t* obj;
        method_t* method;
	};
    any_t() : type(atype_t::ANY_VOID), i8(0) {}
    any_t(atype_t type, int v) : type(type), i8(v) {}
    any_t(atype_t type, float v) : type(type), d(v) {}
    any_t(atype_t type, double v) : type(type), d(v) {}
    any_t(atype_t type, int64_t v) : type(type), i8(v) {}
    any_t(atype_t type, const char* v) : type(type), s(v) {}
    any_t(atype_t type, any_t* v) : type(type), ref(v) {
        add_ref(v);
    }
    any_t(atype_t type, any_t& v) : type(type), ref(&v) {
        add_ref(&v);
    }
    any_t(atype_t type, array_t* v) : type(type), array(v) {
        add_ref(this);
    }
    any_t(atype_t type, dict_t* v) : type(type), dict(v) {
        add_ref(this);
    }
    any_t(atype_t type, pair_t* v) : type(type), pair(v) {}
    any_t(atype_t type, class_obj_t* v) : type(type), obj(v) {}
    explicit any_t(int v) : type(atype_t::ANY_I8), i8(v) {}
    explicit any_t(float v) : type(atype_t::ANY_DOUBLE), d(v) {}
    explicit any_t(double v) : type(atype_t::ANY_DOUBLE), d(v) {}
    explicit any_t(int64_t v) : type(atype_t::ANY_I8), i8(v) {}
    explicit any_t(const char* v) : type(atype_t::ANY_STRING), s(v) {}
    //explicit any_t(any_t* v) : type(atype_t::ANY_REF), ref(v) {
    //    add_ref(v);
    //}
    explicit any_t(array_t* v) : type(atype_t::ANY_ARRAY), array(v) {
        add_ref(this);
    }
    explicit any_t(dict_t* v) : type(atype_t::ANY_DICT), dict(v) {
        add_ref(this);
    }
    explicit any_t(pair_t* v) : type(atype_t::ANY_PAIR), pair(v) {}
    explicit any_t(class_obj_t* v) : type(atype_t::ANY_CLASS), obj(v) {}
    explicit any_t(method_t* v) : type(atype_t::ANY_METHOD), method(v) {}
    any_t(const any_t& other) {
        memcpy(this, &other, sizeof(*this));
        add_ref(this);
    }
    any_t& operator = (const any_t& other) {
        sub_ref(this);
        memcpy(this, &other, sizeof(*this));
        add_ref(this);
        return *this;
    }
    bool is_ref() const {
        return type >= atype_t::ANY_ARRAY && type <= atype_t::ANY_DICT;
    }
    ~any_t() {
        if (is_ref() && get_ref(this) > 0)
            sub_ref(this);
    }
    static any_t zero() {
        return any_t(atype_t::ANY_I8, (int64_t)0);
    }
    static any_t none() {
        return any_t(atype_t::ANY_NONE, 0);
    }
    class_obj_t* get_obj();
    void free();
};

bool is_number(const any_t& a);

any_t add_any(const any_t& a, const any_t& b);
any_t sub_any(const any_t& a, const any_t& b);
any_t mul_any(const any_t& a, const any_t& b);
any_t div_any(const any_t& a, const any_t& b);
any_t mod_any(const any_t& a, const any_t& b);
any_t less_any(const any_t& a, const any_t& b);
any_t equal_any(const any_t& a, const any_t& b);
any_t le_any(const any_t& a, const any_t& b);

any_t neg_any(const any_t& a);
any_t not_any(const any_t& a);
any_t equal_any(const any_t& a, int64_t i8);
any_t equal_any(const any_t& a, double d);
any_t ne_any(const any_t& a, int64_t i8);
any_t ne_any(const any_t& a, double d);

int print_any(const any_t& v);

int fprint_any(FILE* f, const any_t& v);

int fprint_any_d(FILE* f, const any_t& v);

struct ref_t
{
    int ref = 0;
    void record();
    void remove() const;
    static void print_ref();
};

struct pair_t  // : public ref_t
{
    any_t first;
    any_t second;
};

class class_obj_t: public ref_t
{
protected:
    class_obj_t();
    class_obj_t(int count, class_def_t* type) : count(count), type(type)
    {
        if (type)
            record();
        if (count > 0)
            ptr = (any_t*)calloc(sizeof(*ptr), count);
        else
            ptr = nullptr;
    }
    int count = 0;
    any_t* ptr = nullptr;
    class_def_t* type = nullptr;
public:
    friend void try_free_ref(any_t* a);
    friend void sub_ref(any_t* a);
    void record();
    void remove() const;
    static void print_ref();
    int get_count()
    {
        return count;
    }
    class_def_t* get_type()
    {
        return type;
    }
    void set_type(class_def_t* clz)
    {
        type = clz;
    }
    field_t* get_field(const char* s);
    int get_field_index(const char* s);
    method_t* get_method(const char* s, int count);
    any_t* get(int i)
    {
        if (ptr == nullptr)
            return nullptr;
        if (i < 0 || i >= count)
            return nullptr;
        return &ptr[i];
    }
    any_t* get(const char* s);
    void set(const char* s, const any_t& a);
    void set(int i, const any_t& a)
    {
        if (ptr && i >= 0 && i < count) {
            ptr[i] = a;
        } else {
            assert(0);
        }
    }
    bool push(const any_t& a)
    {
        if (!add(1)) {
            return false;
        }
        ptr[count - 1] = a;
        return true;
    }
    static class_obj_t* create(int count, class_def_t* type)
    {
        return new class_obj_t(count, type);
    }
    bool add(int new_count)
    {
        auto new_ptr = (any_t*)realloc(ptr, sizeof(*ptr) * ((size_t)this->count + new_count));
        if (new_ptr) {
            ptr = new_ptr;
            memset(&ptr[this->count], 0, sizeof(any_t) * new_count);
            this->count += new_count;
            return true;
        }
        return false;
    }
    ~class_obj_t()
    {
        if (ptr) {
            ::free(ptr);
            ptr = nullptr;
        }
    }
    void free()
    {
        if (ptr == nullptr)
            return;
        for (int i = 0; i < count; ++i) {
            ptr[i].free();
        }
        ::free(ptr);
        ptr = nullptr;
    }
};

class array_t: public class_obj_t
{
private:
    array_t();
    array_t(int count, class_def_t* type) : class_obj_t(count, type)
    {
        record();
        if (count > 0)
            ptr = (any_t*)malloc(sizeof(*ptr) * count);
        else
            ptr = nullptr;
    }
public:
    friend void try_free_ref(any_t* a);
    friend void sub_ref(any_t* a);
    void record();
    void remove() const;
    static void print_ref();
    int get_count()
    {
        return count;
    }
    any_t* get(int i)
    {
        if (ptr == nullptr)
            return nullptr;
        if (i < 0 || i >= count)
            return nullptr;
        return &ptr[i];
    }
    static array_t* create(int count)
    {
        return new array_t(count, nullptr);
    }
    bool add(int count)
    {
        auto new_ptr = (any_t*)realloc(ptr, sizeof(*ptr) * ((size_t)this->count + count));
        if (new_ptr) {
            ptr = new_ptr;
            this->count += count;
            return true;
        }
        return false;
    }
};

class dict_t : public class_obj_t
{
private:
    dict_t() : class_obj_t(0, nullptr) {
        record();
    }
    void* tree = nullptr;
public:
    friend void try_free_ref(any_t* a);
    friend void sub_ref(any_t* a);

    void record();
    void remove() const;
    static void print_ref();

    static dict_t* create()
    {
        return new dict_t;
    }
    int get_count() const;
    bool insert(const any_t& key, const any_t& value);
    any_t* find(const any_t& key) const;
    int print(FILE* f, int tab) const;
    ~dict_t();
    void free();
};

void print_ref();

void free_all_ref();

#endif  // __BASE_H__
